﻿// Copyright (c) 2012 Andrei Rinea
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Threading;

using BenchmarkNet;

using NUnit.Framework;

namespace BenchmarkNET_Tests
{
    [TestFixture]
    public class SequentialTests
    {
        private const double AllowedDeviation = 0.05;
        private const uint IterationTimes = 23;
        private const uint FailedIterationTimes = 3;
        private const double ActionDurationMilliseconds = 5;
        private const double FastActionDurationMilliseconds = 0;

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void RejectsNullAction()
        {
            Benchmark.Sequential(null);
        }

        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void RejectsZeroIterations()
        {
            Benchmark.Sequential(() => { }, 0);
        }

        [Test]
        public void CallsActionTheCorrectNumberOfTimes()
        {
            uint actualTimes = 0;
            Benchmark.Sequential(() => { actualTimes++; }, IterationTimes);
            Assert.AreEqual(actualTimes, IterationTimes);
        }

        [Test]
        public void TimingPerIterationIsAccurate()
        {
            var result = TimeIterations();
            var reportedDuration = result.TimePerIteration.TotalMilliseconds;
            var deviation = Math.Abs(1 - reportedDuration / ActionDurationMilliseconds);
            Assert.LessOrEqual(deviation, AllowedDeviation);
        }

        private static BenchmarkResult TimeIterations(double actionDurationMilliseconds = ActionDurationMilliseconds, uint failRequestsCount = 0)
        {
            var actionDuration = TimeSpan.FromMilliseconds(actionDurationMilliseconds);
            uint counter = 0;
            var result = Benchmark.Sequential(() =>
            {
                counter++;
                if (counter > IterationTimes - failRequestsCount)
                {
                    throw new Exception();
                }
                Thread.Sleep(actionDuration);
            }, IterationTimes);
            return result;
        }

        [Test]
        public void TotalTimingIsAccurate()
        {
            var result = TimeIterations();
            var reportedDuration = result.TotalTime.TotalMilliseconds;
            var deviation = Math.Abs(1 - reportedDuration / (ActionDurationMilliseconds * IterationTimes));
            Assert.LessOrEqual(deviation, AllowedDeviation);
        }

        [Test]
        public void IterationsMadeReportsCorrectIfAllSucceeded()
        {
            var result = TimeIterations(FastActionDurationMilliseconds);
            Assert.AreEqual(IterationTimes, result.IterationsMade);
        }

        [Test]
        public void IterationsMadeReportsCorrectIfOnlySomeSucceeded()
        {
            var result = TimeIterations(FastActionDurationMilliseconds, FailedIterationTimes);
            Assert.AreEqual(IterationTimes - FailedIterationTimes, result.IterationsMade);
        }

        [Test]
        public void ReportsCompletedSuccessfulOnFullySuccessful()
        {
            var result = TimeIterations(FastActionDurationMilliseconds);
            Assert.IsFalse(result.ErrorsEncountered);
        }

        [Test]
        public void ReportsCompletedUnsuccessfulOnPartiallySuccessful()
        {
            var result = TimeIterations(FastActionDurationMilliseconds, FailedIterationTimes);
            Assert.IsTrue(result.ErrorsEncountered);
        }
    }
}